#include "img.hpp"

#define cimg_display 1 // X11-based display.
#define cimg_verbosity 0 // No debug messages.
#include "CImg.h"

#include <stdexcept>

using namespace cimg_library;

image::~image()
{
	CImg<unsigned char>* img = static_cast<CImg<unsigned char>*>(cimg);
	delete img;
	cimg = (CImg<unsigned char>*)0;
}

void image::canny_edge_detection(float alpha)
{
	if (!cimg)
		return;

	CImg<unsigned char>* img = static_cast<CImg<unsigned char>*>(cimg);

	// normalize our image the get the grayscale.
	CImg<> visu_bw = img->get_norm().normalize(
	                 0, 255).resize(img->width(),
	                                img->height(), 1, 2, 2);

	// grad[0] is the gradient image of 'visu_bw' in x-direction.
	// grad[1] is the gradient image of 'visu_bw' in y-direction.
	CImgList<> grad(visu_bw.blur((float)alpha).normalize(0, 255).
	           get_gradient());

	// avoiding unnecessary calculations.
	const double p8  = M_PI/8.0;
	const double p38 = 3.0*p8;
	const double p58 = 5.0*p8;
	const double p78 = 7.0*p8;

	double Gs, Gt, Gst;
	double Gr, Gur, Gu, Gul, Gl, Gdl, Gd, Gdr;
	// ^-- right, up right, up, up left, left, down left, down, down right

	const unsigned char black[3] = { 0, 0, 0 };

	// angle (in radians) used to locate the direction of the edge.
	double theta;

	// resulting image.
	CImg<unsigned char> edge(img->width(), img->height());
	edge.fill(255); // fill background with white

	cimg_forXY(visu_bw, x, y) {
		if (x >= 1 && x <= visu_bw.width()-1 && y >= 1 &&
		    y <= visu_bw.height()-1) {
			Gs = grad[0](x, y);
			Gt = grad[1](x, y);
			Gst = cimg::abs(Gs) + cimg::abs(Gt);
			theta = std::atan2(Gt, Gs)+M_PI;

			// rounding
			if (theta >= M_PI) {
				theta -= M_PI;
			}

			if ((p8 < theta && theta <= p38 ) || (p78 < theta &&
			    theta <= M_PI)) {
				Gr = cimg::abs(grad[0](x+1,y)) +
				     cimg::abs(grad[1](x+1,y));
				Gl = cimg::abs(grad[0](x-1,y)) +
				     cimg::abs(grad[1](x-1,y));
				if (Gr < Gst && Gl < Gst) {
					edge.draw_point(x, y, black);
				}
			} else if ( p8 < theta && theta <= p38) {
				Gur = cimg::abs(grad[0](x+1,y+1)) +
				      cimg::abs(grad[1](x+1,y+1));
				Gdl = cimg::abs(grad[0](x-1,y-1)) +
				      cimg::abs(grad[1](x-1,y-1));
				if (Gur < Gst && Gdl < Gst) {
					edge.draw_point(x, y, black);
				}
			} else if ( p38 < theta && theta <= p58) {
				Gu = cimg::abs(grad[0](x,y+1)) +
				     cimg::abs(grad[1](x,y+1));
				Gd = cimg::abs(grad[0](x,y-1)) +
				     cimg::abs(grad[1](x,y-1));
				if (Gu < Gst && Gd < Gst) {
					edge.draw_point(x, y, black);
				}
			} else if (p58 < theta && theta <= p78) {
				Gul = cimg::abs(grad[0](x-1,y+1)) +
				      cimg::abs(grad[1](x-1,y+1));
				Gdr = cimg::abs(grad[0](x+1,y-1)) +
				      cimg::abs(grad[1](x+1,y-1));
				if (Gul < Gst && Gdr < Gst) {
					edge.draw_point(x, y, black);
				}
			}
		}
	}

	*img = edge;
}

void image::display()
{
	if (!cimg)
		return;

	CImg<unsigned char>* img = static_cast<CImg<unsigned char>*>(cimg);
	CImgDisplay display(*img, "Press Esc to exit.");

	while (!display.is_closed()) {
		display.wait();
		if (display.is_keyESC())
			display.close();
	}
}

void image::equalize(const unsigned int nb_levels, const char value_min,
              const char value_max)
{
	if (!cimg)
		return;

	CImg<unsigned char>* img = static_cast<CImg<unsigned char>*>(cimg);
	*img = img->get_equalize(nb_levels);
}

unsigned char* image::getData()
{
	CImg<unsigned char>* img = static_cast<CImg<unsigned char>*>(cimg);
	return (img ? img->data() : 0);
}

size_t image::getDepth()
{
	CImg<unsigned char>* img = static_cast<CImg<unsigned char>*>(cimg);
	return (img ? img->depth() : 0);
}

size_t image::getHeight()
{
	CImg<unsigned char>* img = static_cast<CImg<unsigned char>*>(cimg);
	return (img ? img->height() : 0);
}

size_t image::getSpectrum()
{
	CImg<unsigned char>* img = static_cast<CImg<unsigned char>*>(cimg);
	return (img ? img->spectrum() : 0);
}

size_t image::getWidth()
{
	CImg<unsigned char>* img = static_cast<CImg<unsigned char>*>(cimg);
	return (img ? img->width() : 0);
}

void image::load(const char *filename)
{
	try
	{
		cimg = static_cast<void*>(new CImg<unsigned char>(filename));
	}
	catch (CImgIOException& ex)
	{
		throw std::runtime_error(ex.what());
	}
}

void image::toGrayscale()
{
	if (!cimg)
		return;

	CImg<unsigned char>* img = static_cast<CImg<unsigned char>*>(cimg);
	*img = img->get_norm().normalize(0, 255);
}

void image::normalize()
{
	if (!cimg)
		return;

	CImg<unsigned char>* img = static_cast<CImg<unsigned char>*>(cimg);
	*img = img->normalize(0, 255);
}

void image::quantize(const unsigned int nb_levels,
                     const bool keep_range)
{
	if (!cimg)
		return;

	CImg<unsigned char>* img = static_cast<CImg<unsigned char>*>(cimg);
	*img = img->quantize(nb_levels, keep_range);
}

void image::sharpen(const float amplitude, const bool sharpen_type,
                    const float edge, const float alpha,
                    const float sigma)
{
	if (!cimg)
		return;

	CImg<unsigned char>* img = static_cast<CImg<unsigned char>*>(cimg);
	*img = img->get_sharpen(amplitude, sharpen_type, edge, alpha, sigma);
}

